package com.bc.commonentity.log;

import com.bc.commoncore.util.ParseHelper;
import com.bc.commoncore.util.SpringBeanHelper;
import com.bc.commoncore.util.StringUtils;
import com.bc.commonentity.common.CustomSqlSessionTemplate;
import com.bc.commonentity.common.DBContextDbType;
import com.bc.commonentity.common.DBContextHolder;
import com.github.orderbyhelper.OrderByHelper;
import com.github.pagehelper.Page;
import com.github.pagehelper.SqlUtil;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.defaults.DefaultSqlSession.StrictMap;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class MybatisSqlHelper {
	protected static final Logger logger = LoggerFactory.getLogger(MybatisSqlHelper.class);
	private static volatile Configuration singleConfiguration=null;
	private static Object singleConfigurationLocker=new Object();
	private static Configuration getSingleConfiguration(SqlSessionFactory readOnlyMySqlSessionFactory){
		if(singleConfiguration==null){
			synchronized (singleConfigurationLocker){
				if(singleConfiguration==null){
					try {
						if(readOnlyMySqlSessionFactory!=null){
							singleConfiguration= readOnlyMySqlSessionFactory.getConfiguration();
						}else{
							CustomSqlSessionTemplate sqlSessionTemplate=SpringBeanHelper.getCustomBeanByType(CustomSqlSessionTemplate.class);
							singleConfiguration= sqlSessionTemplate.getSqlSessionFactory().getConfiguration();
						}
					}catch(Exception ex){
						logger.error("获取sql参数Configuration对象时出错:"+ex.getMessage()+ StringUtils.getStackTrace(ex));
						ErrorEmail.sendErrorMail("获取sql参数Configuration对象时出错",ex);
					}
				}
			}
		}
		return singleConfiguration;
	}
	/**
     * 获取将要执行的sql语句
     * @param boundSql
     * @param parameterObject
     * @param sqlString
     * @date 20160623
     * @author zhaohailong
     * @return
     */
    public static String getSql(Configuration configuration,BoundSql boundSql,Object parameterObject,String sqlString,Class<?> returnType){
    	if (boundSql==null||sqlString.isEmpty()) {
			return "";
		}
		String targetSql=fillSql(configuration,boundSql,parameterObject,sqlString);
		Page page=SqlUtil.getLocalPage();
		if (page!=null) {//此时是分页
			targetSql=String.format("select count(*) from (%s) tmp_count;\n%s limit %s offset %s ",
					targetSql,targetSql,page.getPageSize(),page.getStartRow());
		}

		String dbType=DBContextHolder.getDbType();
		targetSql=(dbType==null?"默认":dbType)+"级别:"+targetSql;
		//logger.debug(targetSql);
		return targetSql;
    }
    /**
     * 将sql的参数填充到sql语句中
     * @param configuration
     * @param boundSql
     * @param parameterObject
     * @param sqlString
     * @return
      * @author zhaohailong
      * @date 20160627
     */
    private static String fillSql(Configuration configuration,BoundSql boundSql,Object parameterObject,String sqlString){
    	if (sqlString.indexOf("?")<0) {
			return sqlString;
		}
    	List<String> values=getValuesList(configuration,boundSql,parameterObject);
    	if (values==null||values.size()==0) {
			throw new RuntimeException("解析sql语句时异常，没取到参数");
		}
	    Matcher m = Pattern.compile("\\?").matcher(sqlString);
	    StringBuffer sb = new StringBuffer();
	    int index=0;
	    while(m.find()) {
	    	if (index>=values.size()) {
				break;
			}
			try{
				m.appendReplacement(sb, values.get(index));
			}catch (Exception ex){
				logger.error("解析sql时异常(值包含特殊字符):sql="+sqlString+";异常值="+values.get(index));
			}
	        index++;
	    }
	    m.appendTail(sb);
	    return sb.toString();
    }

    /**
     * 获取sql语句中需要的参数的值
     * @param configuration
     * @param boundSql
     * @param parameterObject
     * @return
     * @author zhaohailong
     * @date 20160627
     */
    private static List<String> getValuesList(Configuration configuration,BoundSql boundSql,Object parameterObject){
    	List<String> valuesList = new ArrayList<>();
    	if (parameterObject==null) {
			return valuesList;
		}
    	List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
		if (parameterMappings != null&&parameterMappings.size()>0) {
			String targetValue;
			Object value;
			String propertyName;
			TypeHandlerRegistry typeHandlerRegistry=configuration.getTypeHandlerRegistry();
			MetaObject metaObject = configuration.newMetaObject(parameterObject);
			for (int i = 0; i < parameterMappings.size(); i++) {
				ParameterMapping parameterMapping = parameterMappings.get(i);
				if (parameterMapping.getMode() != ParameterMode.OUT) {
					propertyName= parameterMapping.getProperty();
					if (boundSql.hasAdditionalParameter(propertyName)) { 
						value = boundSql.getAdditionalParameter(propertyName);
					}else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
			            value = parameterObject;
			        } else {
						value = metaObject.getValue(propertyName);
					}
					if (value==null) {
						targetValue = "NULL";
					}else if (value instanceof Date) {
			    		targetValue = String.format("'%s'", ParseHelper.ToDateString((Date) value));
			    	}else if (value instanceof String) {
			    		targetValue = String.format("'%s'", value);
					}else {
						targetValue = value.toString();
					}
					valuesList.add(targetValue);
				}
			}
		}
		return valuesList;
    }
	/**
	 * 执行dao层sql语句之前，获取sql语句
	 * 如果sql语句中包含update,insert,delete等关键字，则强制切换到主库
	 * @param point
	 * @return 最终生成的sql语句
	 * @throws Exception
	 * @author zhaohailong
	 * @date 20160627
	 */
    public static String before(JoinPoint point,SqlSessionFactory readOnlyMySqlSessionFactory) throws Exception {
    	try{
			//获取sql参数
			Configuration configuration=getSingleConfiguration(readOnlyMySqlSessionFactory);
			if(configuration==null){
				return "";
			}
			MethodSignature signature = (MethodSignature) point.getSignature();
			ZhtxMethodSignature method=new ZhtxMethodSignature(configuration, signature.getMethod());
			Object tempParam = method.convertArgsToSqlCommandParam(point.getArgs());
			Object parameterObject = wrapCollection(tempParam);

//	        String sqlid=signature.getDeclaringTypeName()+"."+signature.getMethod().getName();
			// 20180110 李大明
			// 当dao接口存在集成关系的时候，需要解决sqlid精确查找
			Class<?>[] classz = point.getTarget().getClass().getInterfaces();
			String sqlid = "";
			if (classz.length > 0) {
				sqlid = classz[0].getName() + "."
						+ signature.getMethod().getName();
			} else {
				sqlid = signature.getDeclaringTypeName() + "."
						+ signature.getMethod().getName();
			}
			BoundSql boundSql= configuration.getMappedStatement(sqlid).getBoundSql(parameterObject);
			String sqlString=boundSql.getSql();
			String compareSql=sqlString.toLowerCase();
			if (compareSql.indexOf("update")>=0||
					compareSql.indexOf("insert")>=0||
					compareSql.indexOf("delete")>=0||
					compareSql.indexOf("/*master*/")>=0
					) {
				logger.debug("====根据sql语句(update、insert、delete、/*master*/)切换数据源--为Master级别====");
				DBContextHolder.setDbType(DBContextDbType.Master.name());
			}
			return getSql(configuration,boundSql,parameterObject,sqlString,signature.getMethod().getReturnType());
		}catch (Exception e){
			//如果在执行sql之前,发现分页信息或参数异常，则抛出异常，中断sql的执行
			//此时需要在抛出异常之前，先清理一下分页信息
			//否则会导致分页信息被错误的应用到本次请求（或落在当前线程的下次请求）的下一个sql语句上
			//相当于调用SqlUtil.processPage()中的finally块
			SqlUtil.clearLocalPage();
			OrderByHelper.clear();
    		throw e;
		}
    }
    public static Object wrapCollection(final Object object) {
        if (object instanceof Collection) {
          StrictMap<Object> map = new StrictMap<Object>();
          map.put("collection", object);
          if (object instanceof List) {
            map.put("list", object);
          }
          return map;
        } else if (object != null && object.getClass().isArray()) {
          StrictMap<Object> map = new StrictMap<Object>();
          map.put("array", object);
          return map;
        }
        return object;
      }
    public static void after(JoinPoint point) throws Exception {
    	logger.debug("切换数据源--为默认级别");
        DBContextHolder.clearDBType();
    }
}
